package com.ls.fw.web.file.controller;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.multipart.support.DefaultMultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import com.ls.fw.commons.core.unit.ByteSizeValue;
import com.ls.fw.commons.core.utils.file.FileUtil;
import com.ls.fw.data.bean.ResultStatus;
import com.ls.fw.data.bean.ResultTemplate;
import com.ls.fw.mvc.springmvc.base.BaseCRUDController;
import com.ls.fw.mvc.springmvc.util.ViewUtil;
import com.ls.fw.web.file.bean.FileInfo;
import com.ls.fw.web.file.support.config.IFileConfiguration;
import com.ls.fw.web.file.support.exception.FileInValidException;

/**
 * 
 * 
 * @author lisheng
 * @date 2015年6月27日 下午3:59:42
 * @version V1.0
 */
public abstract class BaseFileController extends BaseCRUDController {
	public static final long BASE_SIZE = 1024;

	// 1GB
	public static final long MAX_SIZE = BASE_SIZE * BASE_SIZE * BASE_SIZE * 1;

	/** 保存文件名称 */
	public static final String SAVENAME = "savename";

	/** 保存文件相对目录 */
	public static final String RELATIVEDIR = "dir";
	
	public static final String PATH = "path";

	protected boolean isAbsolutePath = false;

	/**
	 * 是否加密
	 */
	protected boolean encrypt = false;
	
	
	/**
	 * 配置文件信息
	 * @author lisheng
	 * @date 2016年5月1日 下午12:04:16
	 * @version V1.0
	 * @return
	 */
	protected abstract IFileConfiguration getFileConfiguration();
	
	/**
	 * 文件下载的链接
	 * @author lisheng
	 * @date 2016年5月1日 下午12:03:57
	 * @version V1.0
	 * @return
	 */
	protected abstract String getFileDownloadBaseRequestMappingValue();
    
	
	
	/**
	 * 检查文件是否有效
	 * 
	 * @author lisheng
	 * @date 2016年4月26日 下午9:02:15
	 * @version V1.0
	 * @param name
	 * @param file
	 * @param request
	 * @throws Exception 
	 */
	protected void isValid(IFileConfiguration fileLimit, MultipartFile file,
			HttpServletRequest request)
			throws Exception {
		boolean valid = true;
		String fileName = file.getOriginalFilename();
		String ext = FileUtil.getFileExtension(fileName, false);
		if (!isValidExt(ext)) {
			valid = false;
			throw new FileInValidException(ResultStatus.BAD_REQUEST,"不允许上传以【" + ext + "】为后缀的文件！");
		}
		if (valid && fileLimit.getMaxFileSize() > 0) {
			long size = file.getSize();
			if (size > fileLimit.getMaxFileSize()) {
				ByteSizeValue bsv = new ByteSizeValue(
						fileLimit.getMaxFileSize());
				ByteSizeValue fileSize = new ByteSizeValue(size);
				valid = false;
				throw new FileInValidException(ResultStatus.BAD_REQUEST,fileName+"大小："+fileSize.toString()+"不允许上传超过" + bsv.toString() + "的文件！");
			}
		}
	}

	protected ModelAndView doUpload(DefaultMultipartHttpServletRequest request, WebRequest webRequest
			, String viewName, UploadWorkHandler mapper) throws Exception{
		ResultTemplate obj = mapper.doWork(request,webRequest);
		return ViewUtil.convert(this.returnResult(obj, viewName), webRequest,obj.isForceJson());
	}
	
	/**
	 * 上传文件处理器
	 * 
	 * @author lisheng
	 * @date 2015年9月11日 下午7:17:36
	 * @version V1.0
	 * @param request
	 * @param handler
	 * @throws Exception
	 */
	protected  void uploadHandler(DefaultMultipartHttpServletRequest request, FileUploadHandler handler
			) throws Exception {
		//创建一个通用的多部分解析器
        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getServletContext());
        //判断 request 是否有文件上传,即多部分请求
        if(multipartResolver.isMultipart(request)){
           Map<String,MultipartFile> map =  request.getFileMap();
            //取得request中的所有文件名
            Iterator<Entry<String, MultipartFile>> iter = map.entrySet().iterator();
            while(iter.hasNext()){
                //记录上传过程起始时的时间，用来计算上传时间
                int pre = (int) System.currentTimeMillis();
                Entry<String, MultipartFile> e = iter.next();
                //取得上传文件
                String name = e.getKey();
                MultipartFile file = e.getValue();
                if(file != null){
                	String dir = getSaveRelativeDir(request);
                	String saveName = getSaveName(request,file);
                    try {
                    	handler.check(getFileConfiguration(),file,  request,dir,saveName);
                    	FileInfo fileInfo = handler.save(name,dir,saveName,file,request);
						handler.success(fileInfo, name,dir,saveName, file, request);
					} catch (Exception e1) {
						handler.fail(e1, name,dir,saveName, file, request);
					}
                }
                //记录上传该文件后的时间
                int finaltime = (int) System.currentTimeMillis();
                this.logger.debug("保存"+file.getOriginalFilename()+"耗时："+(finaltime - pre)/1000.0+"s");
            }
        }
    } 
	
    /**
     * 文件上传处理器
     * @author lisheng
     */
    public interface FileUploadHandler{
    	
    	/**
    	 * 检查文件是否有效
    	 * @author lisheng
    	 * @date 2016年4月27日 下午8:19:19
    	 * @version V1.0
    	 * @param file
    	 * @param request
    	 * @throws Exception
    	 */
    	void check(IFileConfiguration fileConfiguration, MultipartFile file, HttpServletRequest request,String saveDir,String saveName) throws Exception;
    	
    	/**
    	 * 保存文件
    	 * @author lisheng
    	 * @date 2015年10月12日 下午8:27:05
    	 * @version V1.0
    	 * @param fileFieldName 文件域名
    	 * @param file 文件
    	 * @param request
    	 * @return
    	 * @throws Exception
    	 */
    	FileInfo save(String fileFieldName,String saveDir,String saveName, MultipartFile file, HttpServletRequest request
        		 ) throws Exception;
        

    	/**
    	 * 保存文件失败
    	 * 
    	 * @author lisheng
    	 * @date 2016年4月27日 下午8:15:43
    	 * @version V1.0
    	 * @param e
    	 * @param fileFieldName
    	 * @param file
    	 * @param request
    	 * @throws Exception
    	 */
        void fail(Exception e,String fileFieldName,String saveDir,String saveName, MultipartFile file, HttpServletRequest request
        		 ) throws Exception;
        
        
        
        /**
         * 保存成功
         * 
         * @author lisheng
         * @date 2016年4月27日 下午8:16:10
         * @version V1.0
         * @param fileInfo
         * @param fileFieldName
         * @param file
         * @param request
         * @throws Exception
         */
        void success(FileInfo fileInfo, String fileFieldName,String saveDir,String saveName, MultipartFile file, HttpServletRequest request
       		 ) throws Exception;
    }
    
    

	/**
	 * 上传文件处理器
	 * 
	 * @author lisheng
	 * @date 2015年10月12日 下午8:39:41
	 * @version V1.0
	 */
	public interface UploadWorkHandler extends WorkHandler{
		
		ResultTemplate doWork(DefaultMultipartHttpServletRequest request, WebRequest webRequest) throws Exception;
	}

   
	protected FileInfo saveFile(HttpServletRequest request, MultipartFile file, String dir, String name, String downloadBaseUrl) throws IllegalStateException, IOException {
		return saveFile(request,file,dir,name,downloadBaseUrl,null,null);
	}
	/**
	 * 保存文件
	 * 
	 * @author lisheng
	 * @date 2016年4月21日 下午10:26:07
	 * @version V1.0
	 * @param file
	 * @param dir
	 * @param name
	 * @param downloadBaseUrl 
	 * @return
	 * @throws IllegalStateException
	 * @throws IOException
	 */
	protected FileInfo saveFile(HttpServletRequest request, MultipartFile file, String dir, String name, String downloadBaseUrl,String removeBaseUrl,String deleteBaseUrl) throws IllegalStateException, IOException {
		String path = getFileConfiguration().getBaseDir();
		String baseRelativeDir = dir;
		if(StringUtils.isNotBlank(baseRelativeDir)){
			path = getFileConfiguration().getBaseDir() + File.separator + baseRelativeDir;
		}
		String saveName = name;
		if (StringUtils.isBlank(saveName)) {
			saveName = file.getOriginalFilename();
		}
		String newFileName = FileUtil.getFinalFileName(path, saveName);
		boolean flag = false;
		if (!newFileName.equals(saveName)) {
			flag = true;
		}
		path = this.mkDirIfNotExist(path);
		File destfile = new File(path, newFileName);
		file.transferTo(destfile);
		destfile.setExecutable(false);
		FileInfo fileInfo = this.buildFileInfo(file,
				baseRelativeDir, newFileName, flag,downloadBaseUrl,removeBaseUrl,  deleteBaseUrl);
		return fileInfo;
	}
	

	/**
	 * 创建上传的文件实体
	 * 
	 * @author lisheng 2014年2月22日 上午12:39:03
	 * @param item
	 * @param saveDir
	 * @param saveName
	 * @param existCommonName
	 * @return
	 */
	private FileInfo buildFileInfo(final MultipartFile file,
			String saveRelativeDir, String saveName
			, boolean existCommonName,String downloadBaseUrl
			,String removeBaseUrl,String deleteBaseUrl) {
		FileInfo fileInfo = new FileInfo();
		fileInfo.setContentType(file.getContentType());
		fileInfo.setSize(file.getSize());
		ByteSizeValue bsv = new ByteSizeValue(file.getSize());
		fileInfo.setSizeStr(bsv.toString());
		String fileName = file.getOriginalFilename();
		fileInfo.setSrcName(fileName);
		fileInfo.setFileType(FileUtil.getFileExtension(fileName, false));
		fileInfo.setSaveRelativeDir(saveRelativeDir);
		fileInfo.setSaveName(saveName);
		fileInfo.setExistSameName(existCommonName);
		if(StringUtils.isNotBlank(saveRelativeDir)){
			fileInfo.setRelativePath(StringUtils.replace(saveRelativeDir, "\\", "/")+"/"+saveName);
			
		}else{
			try {
				fileInfo.setRelativePath(URLEncoder.encode(saveName, "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				fileInfo.setRelativePath(saveName);
			}
		}
		try {
			fileInfo.setUrl(downloadBaseUrl+"/"+URLEncoder.encode(fileInfo.getRelativePath(), "UTF-8"));
		    if(StringUtils.isNotBlank(deleteBaseUrl)){
			 fileInfo.setDeleteUrl(deleteBaseUrl+"/"+URLEncoder.encode(fileInfo.getRelativePath(), "UTF-8"));
		    }
		    if(StringUtils.isNotBlank(removeBaseUrl)){
			  fileInfo.setRemoveUrl(removeBaseUrl+"/"+URLEncoder.encode(fileInfo.getRelativePath(), "UTF-8"));
		     }
		} catch (UnsupportedEncodingException e) {
			fileInfo.setUrl(downloadBaseUrl+"/"+(fileInfo.getRelativePath()));
			if(StringUtils.isNotBlank(deleteBaseUrl)){
				fileInfo.setDeleteUrl(deleteBaseUrl+"/"+(fileInfo.getRelativePath()));
			}
			if(StringUtils.isNotBlank(removeBaseUrl)){
				fileInfo.setRemoveUrl(removeBaseUrl+"/"+(fileInfo.getRelativePath()));
			}
		}
		 
		return fileInfo;
	}
	
	
	/**
	 * 保存相对目录
	 * 
	 * @author Defender 2014年6月19日下午9:46:13
	 * @param request
	 * @return
	 */
	protected String getSaveRelativeDir(HttpServletRequest request) {
		String dir = request.getParameter(RELATIVEDIR);
		dir = toLocalSeparator(dir);
		if (StringUtils.isNotBlank(dir)) {
			if (dir.startsWith(File.separator)) {
				dir = StringUtils.replaceOnce(dir,File.separator, "");
			}
			if (dir.endsWith(File.separator)) {
				int index = dir.lastIndexOf(File.separator);
				dir = dir.substring(0, index);
			}
		}
		if (StringUtils.isBlank(dir)) {
			dir = "";
		}
		while (dir.startsWith("../")) {
			dir = dir.replaceFirst("../", "");
		}
		while (dir.startsWith("..\\")) {
			dir = dir.replaceFirst("..\\\\", "");
		}
		return dir;
	}
	
	protected String getSaveName(HttpServletRequest request,MultipartFile file) {
		String name = request.getParameter(SAVENAME);
		if (StringUtils.isBlank(name)) {
			name = file.getOriginalFilename();
		}
		return name;
	}
	
	/**
	 * 是否是允许的文件后缀
	 * 
	 * @author lisheng 2014年2月21日 下午10:33:51
	 * @param ext
	 * @return
	 */
	protected boolean isValidExt(final String ext) {
		boolean flag = false;
		String[] type = this.getFileConfiguration().getDenyType();
		boolean deny = false;
		boolean allow = true;
		if (type != null) {
			deny = this.match(type, ext);
		}
		if (!deny) {
			type = null;
			type = this.getFileConfiguration().getAllowType();
			if (type != null) {
				allow = this.match(type, ext);
			}
		}
		if (allow && !deny) {
			flag = true;
		}
		return flag;
	}

	/**
	 * 上传文件类型是否匹配要求的
	 * 
	 * @author lisheng 2014年2月21日 下午10:17:31
	 * @param type
	 * @param extension
	 * @return
	 */
	private boolean match(final String[] type, final String extension) {
		boolean flag = false;
		String str = ("*." + extension).toUpperCase();
		if (type != null) {
			for (String string : type) {
				if (!string.startsWith("*.")) {
					string = "*." + string;
				}
				if (str.equalsIgnoreCase(string.toUpperCase())) {
					flag = true;
					break;
				}
			}
		}
		return flag;
	}
	 
	/**
	 * 检查目录是否存在
	 * 
	 * @author lisheng
	 * @date 2015年10月16日 下午9:33:04
	 * @version V1.0
	 * @param dirPath
	 * @return
	 */
	private String mkDirIfNotExist(final String dirPath) {
		String dir = dirPath;
		try {
			dir = this.toLocalSeparator(dir);
			File file = new File(dir);
			if (!file.exists()) {
				file.mkdirs();
			}
		} catch (Exception e) {
			this.logger.info(e.getMessage() + ",请设置下载文件目录！", e);
		}
		return dir;
	}

	/**
	 * 转换为本地分隔符
	 * 
	 * @author lisheng
	 * @date 2015年10月16日 下午9:33:26
	 * @version V1.0
	 * @param dirPath
	 * @return
	 */
	private String toLocalSeparator(final String dirPath) {
		String dir = dirPath;
		if (StringUtils.isBlank(dir)) {
			dir = "";
		}
		try {
			dir = StringUtils.replaceChars(dir, "\\", File.separator);
			dir = StringUtils.replaceChars(dir, "/", File.separator);
		} catch (Exception e) {
			this.logger.info(e.getMessage() + ",请设置下载文件目录！", e);
		}
		return dir;
	}
	
}





